import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {Observable, TimeoutError} from 'rxjs';
import {catchError, concatMap, map, timeout} from 'rxjs/operators';
import {NzModalRef, NzModalService} from 'ng-zorro-antd/modal';
import {ErrorModalComponent} from './error-modal/error-modal.component';
import {environment} from '../../../environments/environment';
import {StringTool} from '../../tools';
import {ResponseEntity} from './response-entity';
import {LoginUserService} from '../login-user.service';
import {LoginUser} from '../../entities/user/login-user';
import {Router} from '@angular/router';
import {NzMessageService} from 'ng-zorro-antd/message';

interface RequestOption {
  headers?: HttpHeaders | {
    [header: string]: string | string[];
  };
  observe?: 'body';
  params?: HttpParams | {
    [param: string]: string | string[];
  };
  reportProgress?: boolean;
  responseType?: 'json';
  withCredentials?: boolean;
}

/**
 * 对HttpClient的封装
 * 1. 统一处理错误
 * 2. token
 * 3. 分页参数
 */
@Injectable({
  providedIn: 'root'
})
export class HttpUtilService {

  private static readonly servicePrefix = environment.service.servicePrefix;
  private static readonly timeout = environment.httpConfig.timeout;
  private static readonly current = environment.page.current;
  private static readonly pageSize = environment.page.pageSize;

  private static modalHook: NzModalRef<ErrorModalComponent, any> = null;
  // 需要在 appModule的 imports 数组中加入 NzModalModule，或者在providers数组中加入NzModalService ，
  // 因为 HttpUtilService 中在root 中注入的，所以也在确保在 root中有 NzModalService 实例
  constructor(private http: HttpClient, private modal: NzModalService, private loginUserService: LoginUserService,
              private router: Router, private msg: NzMessageService) {
  }


  /**
   * POST 查询
   * @param url url
   * @param body request body
   * @param handleError  if handle error
   */
  post<T>(url: string, body: any, handleError = true): Observable<T> {
    const httpOptions = this.makePostHeader();
    const uri = StringTool.concatUrl(HttpUtilService.servicePrefix, url);
    return this.http.post<ResponseEntity<T>>(uri, body, httpOptions).pipe(
      timeout(HttpUtilService.timeout),
      map(value => {
        return value.data;
      }),
      catchError((error, caught) => {
        if (error.status === 401) {
          // 刷新token
          return this.retry<T>(handleError, () => {
            const httpOptions2 = this.makePostHeader();
            // 刷新之后继续之前的调用
            return this.http.post<ResponseEntity<T>>(uri, body, httpOptions2).pipe(
              timeout(HttpUtilService.timeout),
              map(value => value.data),
              catchError((err, caught1) => {
                this.errorHandler(err, caught1, url, handleError);
                throw err;
              })
            );
          });

        } else {

          this.errorHandler(error, caught, url, handleError);
          throw error;
        }
      })
    );
  }

  /**
   * POST 查询
   * @param url url
   * @param body request body
   * @param current current page
   * @param pageSize page size
   * @param handleError  if handle error
   */
  postPage<T>(url: string, body: any, current = 0, pageSize = 0, handleError = true): Observable<ResponseEntity<T>> {

    const pageParam = body || {};
    pageParam[HttpUtilService.current] = current;
    pageParam[HttpUtilService.pageSize] = pageSize;

    const httpOptions = this.makePostHeader();
    const uri = StringTool.concatUrl(HttpUtilService.servicePrefix, url);
    return this.http.post<ResponseEntity<T>>(uri, pageParam, httpOptions).pipe(
      timeout(HttpUtilService.timeout),
      map(value => {
        return this.makeResponseBody(value);
      }),
      catchError((error, caught) => {
        if (error.status === 401) {
          // 刷新token
          return this.retry<ResponseEntity<T>>(handleError, () => {
            const httpOptions2 = this.makePostHeader();
            // 刷新之后继续之前的调用
            return this.http.post<ResponseEntity<T>>(uri, body, httpOptions2).pipe(
              timeout(HttpUtilService.timeout),
              map(value => {

                return this.makeResponseBody(value);
              }),
              catchError((err, caught1) => {
                this.errorHandler(err, caught1, url, handleError);
                throw err;
              })
            );
          });
        } else {
          this.errorHandler(error, caught, url, handleError);
          throw error;
        }
      })
    );
  }

  get<T>(url: string, handleError: boolean, param?: any): Observable<T>;

  /**
   * GET 查询
   * @param param url参数
   * @param url url
   * @param handleError  if handle error
   */
  get<T>(url: string,
         handleError = true,
         param?: any): Observable<T> {
    const httpOptions = this.makeHeader();
    if (param) {
      httpOptions.params = param;
    }
    const uri = StringTool.concatUrl(HttpUtilService.servicePrefix, url);
    return this.http.get<ResponseEntity<T>>(uri, httpOptions).pipe(
      timeout(HttpUtilService.timeout),
      map(value => {
        return value?.data;
      }),
      catchError((error, caught) => {
        if (error.status === 401) {

          // 刷新token
          return this.retry<T>(handleError, () => {
            const httpOptions2 = this.makeHeader();
            // 刷新之后继续之前的调用
            return this.http.get<ResponseEntity<T>>(uri, httpOptions2).pipe(
              timeout(HttpUtilService.timeout),
              map(value => value.data)
            );
          }).pipe(
            catchError((err, caught1) => {
              console.log('last error');
              this.errorHandler(err, caught1, url, handleError);
              throw err;
            })
          );
        } else {
          console.log('first error');
          this.errorHandler(error, caught, url, handleError);
          throw error;
        }
      })
    );
  }

  getPage<T>(url: string, current: number, pageSize: number, handleError: boolean, param?: any): Observable<ResponseEntity<T>>;
  getPage<T>(url: string, current = 0, pageSize = 0, handleError = true, param?: any): Observable<ResponseEntity<T>> {

    const httpOptions = this.makeHeader();
    httpOptions.params = param;
    let uri = StringTool.concatUrl(HttpUtilService.servicePrefix, url);

    if (uri.indexOf('?') < 0) {
      uri += '?1=1';
    }
    uri = uri + '&' + HttpUtilService.current + '=' + current + '&' + HttpUtilService.pageSize + '=' + pageSize;

    return this.http.get<ResponseEntity<T>>(uri, httpOptions).pipe(
      timeout(HttpUtilService.timeout),
      map(value => this.makeResponseBody(value)),
      catchError((error, caught) => {
        if (error.status === 401) {

          // 刷新token
          return this.retry<ResponseEntity<T>>(handleError, () => {
            const httpOptions2 = this.makeHeader();
            httpOptions.params = param;
            // 刷新之后继续之前的调用
            return this.http.get<ResponseEntity<T>>(uri, httpOptions2).pipe(
              map(value => this.makeResponseBody(value)),
              timeout(HttpUtilService.timeout)
            );
          }).pipe(
            catchError(err => {
              this.errorHandler(err, caught, url, handleError);
              throw err;
            })
          );
        } else {

          this.errorHandler(error, caught, url, handleError);
          throw error;
        }
      })
    );
  }

  /**
   * 刷新token 再调用指定的接口
   * @param handleError 是否使用默认处理错误
   * @param retry
   * @private
   */
  private retry<T>(handleError: boolean, retry: () => Observable<T>): Observable<T> {
    const refreshUrl = StringTool.concatUrl(HttpUtilService.servicePrefix, 'login/ps/refresh');
    // 刷新token
    return this.http.post<ResponseEntity<LoginUser>>(refreshUrl, {refreshToken: this.loginUserService.refreshToken})
      .pipe(
        timeout(HttpUtilService.timeout),
        map(value => value.data),
        concatMap(value => {
          this.loginUserService.currentUser = value;
          this.loginUserService.refreshToken = value.refreshToken;
          // 刷新之后继续之前的调用
          return retry();
        }),
        catchError(err => {
          if (err.error?.status === 'TOKE_EXPIRED') {
            this.router.navigate(['/login']);
            this.msg.error('登陆超时，请重新登陆！');
          }
          throw err;
        })
      );
  }

  private makeHeader(): RequestOption{
    const token = this.loginUserService.currentUser?.token;
    return {
      headers: new HttpHeaders({
        Authorization: token || ''
      })
    };
  }

  private makePostHeader(): RequestOption {
    const token = this.loginUserService.currentUser?.token;
    return {
      headers: new HttpHeaders({
        Authorization: token || '',
        'Content-Type': 'application/json'
      })
    };
  }

  private makeResponseBody<T>(value: ResponseEntity<T>): ResponseEntity<T> {
    const r = new ResponseEntity<T>();
    r.current = value[environment.page.current];
    r.count = value[environment.page.count];
    r.data = value.data;
    r.status = value.status;
    r.message = value.message;
    r.exception = value.exception;
    return r;
  }

  /**
   * 默认错误处理， 需要在 AppModule中 声明 NzModalModule，ErrorModalComponent，NzMessageModule
   * @param error
   * @param caught
   * @param url
   * @param handleError if show error dialog
   * @private
   */
  private errorHandler<T, O>(error: any, caught: Observable<T>, url: string, handleError: boolean): void {
    if (error instanceof TimeoutError) {
      console.error('An error occurred:', 'timeout');
      this.errorDialog(url, undefined, 'request timeout!', undefined, undefined);
    } else if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
      this.errorDialog(error.url, undefined, error.error.message, undefined, undefined);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong.
      if (handleError) {

        console.error(
          `Backend returned code ${error.status}, ` +
          `body was: ${error.error}`);
        if (error.status === 504) {
          this.errorDialog(url, undefined, 'request timeout!', undefined, undefined);
        } else {
          this.errorDialog(
            error.url, error.status, error.message, error.error?.status,
            error.error?.message + '\n' + error.error?.exception);
        }
      }
    }
  }

  public errorDialog(url = '', status?: number, errorMessage = '', code = '', detail = ''): void {
    if (HttpUtilService.modalHook != null) {
      HttpUtilService.modalHook.close();
    }
    HttpUtilService.modalHook = this.modal.create({
      nzTitle: '请求出错',
      nzContent: ErrorModalComponent,
      nzFooter: null,
      nzComponentParams: {
        url,
        status: status + '',
        errorMessage,
        code,
        detail
      },
    });
  }
}
